//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 
// 	FILE:		TransformObject.cpp
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "transformObject.h"


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::setDimensions( GLfloat w, GLfloat h )
{
	width = w;
	height = h;
}



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::setInits( float _rad, float _fov ) 
{ 
	 trackRadius = _rad;
	 fov = _fov;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This is actually doing the Sphere/Hyperbolic sheet hybrid thing,
// based on Ken Shoemake's ArcBall in Graphics Gems IV, 1993.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool TransformObject::mapToSphere( const QPoint& _v2D, Vec3f& _v3D )
{
    double x =  (2.0*_v2D.x() - width)/width;
    double y = -(2.0*_v2D.y() - height)/height;
    double xval = x;
    double yval = y;
    double x2y2 = xval*xval + yval*yval;

    const double rsqr = trackRadius * trackRadius;
    _v3D[0] = xval;
    _v3D[1] = yval;

    if( x2y2 < 0.5 * rsqr ) 
        _v3D[2] = sqrt( rsqr - x2y2 );
	else 
        _v3D[2] = 0.5*rsqr/sqrt( x2y2 );
  
    return true;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::storeLastPoint( QPoint _lastPoint )
{
	lastPointOK = mapToSphere( lastPoint2D = _lastPoint, lastPoint3D );
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::activateRotate( const QPoint& newPoint2D )
{
	Vec3f  newPoint3D;
	bool   newPointHitSphere = mapToSphere( newPoint2D, newPoint3D );

	if( lastPointOK &&  newPointHitSphere ) 
	{
		Vec3f axis = lastPoint3D % newPoint3D;

		if( axis.sqrnorm() < 1e-7 ) 
			axis = Vec3f(1,0,0);
		else
			axis.normalize();
		
		// find the amount of rotation
		Vec3f d = lastPoint3D - newPoint3D;
		float t = 0.5 * d.norm() / trackRadius;
		
		if( t<-1.0 ) t = -1.0;
		else if( t>1.0 ) t = 1.0;

		float phi = 2.0 * asin(t);
		float  angle =  phi * 180.0 / PI;
		rotate( axis, angle );
	}
		// remember this point
	lastPoint2D = newPoint2D;
	lastPoint3D = newPoint3D;
	lastPointOK = newPointHitSphere;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Rotate around center center_, axis _axis, by angle _angle
// Update modelview matrix
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::rotate( const Vec3f& _axis, float _angle )
{			 	
	Vec3f t( modelviewMatrix[0]*center[0] + 
		modelviewMatrix[4]*center[1] +
		modelviewMatrix[8]*center[2] + 
		modelviewMatrix[12],
		modelviewMatrix[1]*center[0] + 
		modelviewMatrix[5]*center[1] +
		modelviewMatrix[9]*center[2] + 
		modelviewMatrix[13],
		modelviewMatrix[2]*center[0] + 
		modelviewMatrix[6]*center[1] +
		modelviewMatrix[10]*center[2] + 
		modelviewMatrix[14] );
  
	glLoadIdentity();
	glTranslatef( t[0], t[1], t[2] );
	glRotated( _angle, _axis[0], _axis[1], _axis[2] );
	glTranslatef( -t[0], -t[1], -t[2] ); 
	glMultMatrixd( modelviewMatrix );
	glGetDoublev( GL_MODELVIEW_MATRIX, modelviewMatrix );
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::zoom( float delta )
{
	float d = -delta / 120.0 * 0.2 * radius;
	translate( Vec3f(0.0, 0.0, d) );
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Translate the object by _trans
// Update modelview matrix
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::translate( const Vec3f& _trans )
{
	glLoadIdentity();
	glTranslated( _trans[0], _trans[1], _trans[2] );
	glMultMatrixd( modelviewMatrix );
	glGetDoublev( GL_MODELVIEW_MATRIX, modelviewMatrix);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::moveXY( const QPoint& newPoint2D )
{
	Vec3f  newPoint3D;
	bool   newPointHitSphere = mapToSphere( newPoint2D, newPoint3D );

	float dx = newPoint2D.x() - lastPoint2D.x();
	float dy = newPoint2D.y() - lastPoint2D.y();

	float z = - ( modelviewMatrix[ 2]*center[0] + 
					 modelviewMatrix[ 6]*center[1] + 
					 modelviewMatrix[10]*center[2] + 
					 modelviewMatrix[14] ) /
						( modelviewMatrix[ 3]*center[0] + 
						 modelviewMatrix[ 7]*center[1] + 
						 modelviewMatrix[11]*center[2] + 
						 modelviewMatrix[15] );

	float aspect     = width / height;
	float near_plane = 0.01 * radius;
	float top        = tan( fov/2.0f*PI/180.0f ) * near_plane;
	float right      = aspect*top;

	translate( Vec3f( 2.0*dx/width*right/near_plane*z, -2.0*dy/height*top/near_plane*z, 0.0f ) );

	// remember this point
	lastPoint2D = newPoint2D;
	lastPoint3D = newPoint3D;
	lastPointOK = newPointHitSphere;
}



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::updateProjectionMatrix()
{
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective(fov, width / height, 0.01*radius, 100.0*radius);
	glGetDoublev( GL_PROJECTION_MATRIX, projectionMatrix );
	glMatrixMode( GL_MODELVIEW );
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// view the whole scene: the eye point is moved far
// enough from the center so that the whole scene is 
// visible.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::viewAll( )
{
	translate( Vec3f( -(modelviewMatrix[0]*center[0] + 
		      modelviewMatrix[4]*center[1] +
		      modelviewMatrix[8]*center[2] + 
		      modelviewMatrix[12]),
		    -(modelviewMatrix[1]*center[0] + 
		      modelviewMatrix[5]*center[1] +
		      modelviewMatrix[9]*center[2] + 
		      modelviewMatrix[13]),
		    -(modelviewMatrix[2]*center[0] + 
		      modelviewMatrix[6]*center[1] +
		      modelviewMatrix[10]*center[2] + 
		      modelviewMatrix[14] +
		      3.0*radius) ) );
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Sets the center and size of the whole scene. The _center is used as 
// fixpoint for rotations and for adjusting the camera/viewer 
// (see viewAll()). 	
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TransformObject::setScenePosition( const Vec3f& _cog, float _radius )
{
	center = _cog;
	radius = _radius;

	updateProjectionMatrix();
	viewAll();
}